package at.irian.jsfatwork.web.model;

import at.irian.jsfatwork.domain.BaseEntity;
import at.irian.jsfatwork.web.model.ValidatorMsg;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AnnotationsConfigurer;
import net.sf.oval.configuration.annotation.JPAAnnotationsConfigurer;
import net.sf.oval.localization.MessageResolverImpl;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.apache.myfaces.orchestra.conversation.Conversation;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.List;
import java.util.ResourceBundle;

/**
 * @author Thomas Spiegl, IRIAN Solutions GmbH
 */
public abstract class DetailListBean<T> {
    protected EntityManager _em;

    private int maxResults;
    private String entityName;
    private Class entityClass;

    private List<T> entities;
    private T entity;
    private boolean addNew = false;
    private boolean showAll = false;
    private boolean dirty = false;

    protected DetailListBean(Class entityClass, int maxResults) {
        this.entityClass = entityClass;
        this.maxResults = maxResults;
        this.entityName = this.entityClass.getName();
        int idx = this.entityName.lastIndexOf(".");
        if (idx > 0) {
            this.entityName = this.entityName.substring(idx + 1);
        }
    }

    @PersistenceContext
    public void setEm(EntityManager em) {
        _em = em;
    }

    public boolean isAddNew() {
        return addNew;
    }

    public boolean isShowAll() {
        return showAll;
    }

    public T getEntity() {
        return entity;
    }

    public void setEntity(T value) {
        addNew = false;
        this.entity = value;
    }

    @Transactional(readOnly = true)
    public List<T> getEntities() {
        if (dirty) {
            // validate may throw a RuntimException and therefore detach this.entities
            handleDirty();
        }
        if (this.entities == null) {
            this.entities = readEntityList(this.showAll);
        }
        return this.entities;
    }

    public List<T> getReadAllEntities() {
        // TODO cache
        return readEntityList(true);
    }

    private void handleDirty() {
        if (this.dirty) {
            this.entities = null;
            this.dirty = false;
        }
    }

    private List readEntityList(boolean showAll) {
        Query query = _em.createQuery("select e from " + entityName + " e order by id desc");
        if (!showAll) {
            query.setMaxResults(this.maxResults);
        }
        return query.getResultList();
    }

    public String addNew() {
        addNew = true;
        try {
            entity = (T) entityClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        return null;
    }

    public String toggleShowAll() {
        this.showAll = !this.showAll;
        this.entities = null;
        return null;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public String save(ActionEvent event) {
        validateInternal(event, true);
        _em.merge(entity);
        entity = null;
        if (addNew) {
            this.entities = null;
        }
        addNew = false;
        return null;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public String delete() {
        if (this.entity != null) {
            _em.remove(this.entity);
            this.entity = null;
            this.entities = null;
        }
        return "ok";
    }

    public void validate(ActionEvent event) throws ValidatorException {
        validateInternal(event, false);
    }

    private void validateInternal(ActionEvent event, boolean validateAll) {
        UIComponent component = event.getComponent();
        Validator validator = new Validator(new AnnotationsConfigurer(), new JPAAnnotationsConfigurer());
        MessageResolverImpl mr = (MessageResolverImpl) Validator.getMessageResolver();
        mr.addMessageBundle(ResourceBundle.getBundle("Messages", FacesContext.getCurrentInstance().getViewRoot().getLocale()));
        List<ConstraintViolation> violations = validator.validate(entity);
        ValidatorException ex = null;
        if (violations.size() > 0) {
            ex = new ValidatorException();
        }
        for (ConstraintViolation violation : violations) {
            String field = violation.getContext().toString();
            int idx = field.lastIndexOf('.');
            field = field.substring(idx + 1);
            UIComponent uiComponent = component.findComponent(field);
            String msg = violation.getMessage();
            msg = msg.replace("at.irian.jsfatwork.domain.", "");
            if (uiComponent != null) {
                String clientId = uiComponent.getClientId(FacesContext.getCurrentInstance());
                ex.addMessage(new ValidatorMsg(msg, clientId));
            } else if (validateAll) {
                ex.addMessage(new ValidatorMsg(msg, ""));
            }
        }
        if (ex != null && ex.getMessages().size() > 0) {
            dirty = true;
            throw ex;
        }
    }

    public String cancel() {
        Conversation.getCurrentInstance().invalidate();
        return null;
    }

    @Transactional(readOnly = true)
    public BaseEntity findEntity(Class clazz, Long id) {
        return (BaseEntity) _em.find(clazz, id);
    }
}